using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Examples
{
    public static class EnumerableExtensionMethods
    {
        public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
        {
            foreach (T t in source)
            {
                action(t);
            }
        }

        public static void ForEach<T>(this IEnumerable<T> source, Action<T, int> action)
        {
            int index = 0;
            foreach (T t in source)
            {
                action(t, index++);
            }
        }

        public static List<T> ConvertAll<S, T>(this IEnumerable<S> source, Converter<S, T> converter)
        {
            return new List<S>(source).ConvertAll(converter);
        }

        public static List<T> ConvertAll<S, T>(this IEnumerable<S> source, Func<S, int, T> converter)
        {
            int index = 0;
            List<T> converted = new List<T>();
            foreach (S s in source)
            {
                converted.Add(converter(s, index++));

            }
            return converted;
        }

        public static bool ContainsOnly<T>(this IEnumerable<T> list, IEnumerable<T> other)
        {
            if (list.Count() != other.Count())
            {
                return false;
            }
            return list.FindAll(item => !other.Contains(item)).Count == 0;
        }

        public static T Find<T>(this IEnumerable<T> source, Predicate<T> predicate)
        {
            return new List<T>(source).Find(predicate);
        }

        public static List<T> FindAll<T>(this IEnumerable<T> source, Predicate<T> predicate)
        {
            return new List<T>(source).FindAll(predicate);
        }

        public static bool Exists<T>(this IEnumerable<T> source, Predicate<T> predicate)
        {
            return new List<T>(source).Exists(predicate);
        }

        public static List<T> RemoveAll<T>(this IEnumerable<T> source, Predicate<T> predicate)
        {
            List<T> list = source.ToList();
            list.RemoveAll(predicate);
            return list;
        }

        public static IEnumerable<T> Set<T>(this IEnumerable<T> source)
        {
            return source.Distinct();
        }

        public static T Head<T>(this IEnumerable<T> source)
        {
            return source.First();
        }

        public static List<T> Tail<T>(this IEnumerable<T> source)
        {
            List<T> tail = source.ToList();
            tail.RemoveAt(0);
            return tail;
        }

        public static IEnumerable<List<T>> SubSets<T>(this IEnumerable<T> list)
        {
            if (list.IsEmpty())
            {
                return new List<List<T>> { new List<T>() };
            }
            IEnumerable<List<T>> subsets = list.Tail().SubSets();
            return subsets.Concat(subsets.ConvertAll(subset => new List<T>(subset) { list.Head() }));
        }

        public static string AsString<T>(this IEnumerable<T> list)
        {
            return AsString(list, ",");
        }

        public static string AsString<T>(this IEnumerable<T> list, char delimiter)
        {
            return AsString(list, delimiter.ToString());
        }

        public static string AsString<T>(this IEnumerable<T> list, string delimiter)
        {
            if (IsEmpty(list))
            {
                return string.Empty;
            }

            StringBuilder builder = new StringBuilder().Append(list.Head());
            list.Tail().ForEach(item => builder.Append(delimiter).Append(item));
            return builder.ToString();
        }

        public static string AsString<T, S>(this IEnumerable<T> list, Converter<T, S> converter, string delimiter)
        {
            List<S> convertedList = list.ConvertAll(converter);
            return convertedList.AsString(delimiter);
        }

        public static bool IsEmpty<T>(this IEnumerable<T> list)
        {
            return list.Count() == 0;
        }

        public static List<T> Pad<T>(this IEnumerable<T> list, int minimumLengthOfList, T paddedItem)
        {
            List<T> paddedList = new List<T>(list);

            for (int i = list.Count(); i < minimumLengthOfList; i++)
            {
                paddedList.Add(paddedItem);
            }

            return paddedList;
        }

        public static List<T> ReverseSafely<T>(this IEnumerable<T> list)
        {
            List<T> reversedList = new List<T>(list);
            reversedList.Reverse();
            return reversedList;
        }

        public static List<T> SortSafely<T>(this IEnumerable<T> list)
        {
            List<T> sortedList = new List<T>(list);
            sortedList.Sort();
            return sortedList;
        }

        // This will mutate the current list
        public static void Replace<T>(this List<T> list, Predicate<T> matcher, Converter<T, T> updateMethod)
        {
            int index = list.FindIndex(matcher);
            if (index < 0)
            {
                return;
            }
            list[index] = updateMethod(list[index]);
        }
        // This will mutate the current list
        public static void Replace<T>(this List<T> list, T original, T newItem)
        {
            Replace(list, item => item.Equals(original), found => newItem);
        }
    }
}